/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package egoscale

import (
	"encoding/json"
	"errors"
)

// AsyncJobResult represents an asynchronous job result
type AsyncJobResult struct {
	AccountID       *UUID            `json:"accountid,omitempty" doc:"the account that executed the async command"`
	Cmd             string           `json:"cmd,omitempty" doc:"the async command executed"`
	Created         string           `json:"created,omitempty" doc:"the created date of the job"`
	JobID           *UUID            `json:"jobid" doc:"extra field for the initial async call"`
	JobInstanceID   *UUID            `json:"jobinstanceid,omitempty" doc:"the unique ID of the instance/entity object related to the job"`
	JobInstanceType string           `json:"jobinstancetype,omitempty" doc:"the instance/entity object related to the job"`
	JobProcStatus   int              `json:"jobprocstatus,omitempty" doc:"the progress information of the PENDING job"`
	JobResult       *json.RawMessage `json:"jobresult,omitempty" doc:"the result reason"`
	JobResultCode   int              `json:"jobresultcode,omitempty" doc:"the result code for the job"`
	JobResultType   string           `json:"jobresulttype,omitempty" doc:"the result type"`
	JobStatus       JobStatusType    `json:"jobstatus,omitempty" doc:"the current job status-should be 0 for PENDING"`
	UserID          *UUID            `json:"userid,omitempty" doc:"the user that executed the async command"`
}

// DeepCopy create a true copy of the receiver.
func (a *AsyncJobResult) DeepCopy() *AsyncJobResult {
	if a == nil {
		return nil
	}

	return &AsyncJobResult{
		AccountID:       a.AccountID.DeepCopy(),
		Cmd:             a.Cmd,
		Created:         a.Created,
		JobID:           a.JobID.DeepCopy(),
		JobInstanceID:   a.JobInstanceID.DeepCopy(),
		JobInstanceType: a.JobInstanceType,
		JobProcStatus:   a.JobProcStatus,
		JobResult:       a.JobResult,
		JobResultCode:   a.JobResultCode,
		JobResultType:   a.JobResultType,
		JobStatus:       a.JobStatus,
		UserID:          a.UserID.DeepCopy(),
	}
}

// DeepCopyInto copies the receiver into out.
//
// In (a) must be non nil. out must be non nil
func (a *AsyncJobResult) DeepCopyInto(out *AsyncJobResult) {
	*out = AsyncJobResult{
		AccountID:       a.AccountID.DeepCopy(),
		Cmd:             a.Cmd,
		Created:         a.Created,
		JobID:           a.JobID.DeepCopy(),
		JobInstanceID:   a.JobInstanceID.DeepCopy(),
		JobInstanceType: a.JobInstanceType,
		JobProcStatus:   a.JobProcStatus,
		JobResult:       a.JobResult,
		JobResultCode:   a.JobResultCode,
		JobResultType:   a.JobResultType,
		JobStatus:       a.JobStatus,
		UserID:          a.UserID.DeepCopy(),
	}
}

// ListRequest buils the (empty) ListAsyncJobs request
func (a AsyncJobResult) ListRequest() (ListCommand, error) {
	req := &ListAsyncJobs{
		StartDate: a.Created,
	}

	return req, nil
}

// Error builds an error message from the result
func (a AsyncJobResult) Error() error {
	r := new(ErrorResponse)
	if e := json.Unmarshal(*a.JobResult, r); e != nil {
		return e
	}
	return r
}

// QueryAsyncJobResult represents a query to fetch the status of async job
type QueryAsyncJobResult struct {
	JobID *UUID `json:"jobid" doc:"the ID of the asynchronous job"`
	_     bool  `name:"queryAsyncJobResult" description:"Retrieves the current status of asynchronous job."`
}

// Response returns the struct to unmarshal
func (QueryAsyncJobResult) Response() interface{} {
	return new(AsyncJobResult)
}

//go:generate go run generate/main.go -interface=Listable ListAsyncJobs

// ListAsyncJobs list the asynchronous jobs
type ListAsyncJobs struct {
	Keyword   string `json:"keyword,omitempty" doc:"List by keyword"`
	Page      int    `json:"page,omitempty"`
	PageSize  int    `json:"pagesize,omitempty"`
	StartDate string `json:"startdate,omitempty" doc:"the start date of the async job"`
	_         bool   `name:"listAsyncJobs" description:"Lists all pending asynchronous jobs for the account."`
}

// ListAsyncJobsResponse represents a list of job results
type ListAsyncJobsResponse struct {
	Count    int              `json:"count"`
	AsyncJob []AsyncJobResult `json:"asyncjobs"`
}

// Result unmarshals the result of an AsyncJobResult into the given interface
func (a AsyncJobResult) Result(i interface{}) error {
	if a.JobStatus == Failure {
		return a.Error()
	}

	if a.JobStatus == Success {
		m := map[string]json.RawMessage{}
		err := json.Unmarshal(*(a.JobResult), &m)

		if err == nil {
			if len(m) >= 1 {
				if _, ok := m["success"]; ok {
					return json.Unmarshal(*(a.JobResult), i)
				}

				// otherwise, pick the first key
				for k := range m {
					return json.Unmarshal(m[k], i)
				}
			}
			return errors.New("empty response")
		}
	}

	return nil
}
